// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/public/browser/utility_process_mojo_client.h"

#include <memory>

#include "base/bind.h"
#include "base/callback.h"
#include "base/command_line.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/test_service.mojom.h"

namespace content {

// Test fixture used to make different Mojo calls to the utility process.
class UtilityProcessMojoClientBrowserTest : public ContentBrowserTest {
public:
    void StartMojoService(bool disable_sandbox, bool run_elevated = false)
    {
        mojo_client_.reset(new UtilityProcessMojoClient<mojom::TestService>(
            base::ASCIIToUTF16("TestMojoProcess")));

        mojo_client_->set_error_callback(
            base::Bind(&UtilityProcessMojoClientBrowserTest::OnConnectionError,
                base::Unretained(this)));

        // This test case needs to have the sandbox disabled.
        if (disable_sandbox)
            mojo_client_->set_disable_sandbox();
#if defined(OS_WIN)
        // This test case needs utility process privilege elevation.
        if (run_elevated) {
            CHECK(disable_sandbox);
            mojo_client_->set_run_elevated();
        }
#endif // defined(OS_WIN)

        mojo_client_->Start();
    }

    // Called when a response is received from a call to DoSomething() or
    // DoTerminateProcess().
    void OnResponseReceived()
    {
        response_received_ = true;
        done_closure_.Run();
    }

    // Called when a connection error happen between the test and the utility
    // process.
    void OnConnectionError()
    {
        error_happened_ = true;
        done_closure_.Run();
    }

    // Called when a responsed is received from a call to CreateFolder().
    void OnCreateFolderFinished(bool succeeded)
    {
        response_received_ = true;
        sandbox_succeeded_ = succeeded;
        done_closure_.Run();
    }

protected:
    std::unique_ptr<UtilityProcessMojoClient<mojom::TestService>>
        mojo_client_;
    base::Closure done_closure_;

    // The test result that is compared to the current test case.
    bool response_received_ = false;
    bool error_happened_ = false;
    bool sandbox_succeeded_ = false;
};

// Successful call through the Mojo service with response back.
IN_PROC_BROWSER_TEST_F(UtilityProcessMojoClientBrowserTest, CallService)
{
    base::RunLoop run_loop;
    done_closure_ = run_loop.QuitClosure();

    StartMojoService(false);

    mojo_client_->service()->DoSomething(
        base::Bind(&UtilityProcessMojoClientBrowserTest::OnResponseReceived,
            base::Unretained(this)));

    run_loop.Run();
    EXPECT_TRUE(response_received_);
    EXPECT_FALSE(error_happened_);
}

// Call the Mojo service but the utility process terminates before getting
// the result back.
IN_PROC_BROWSER_TEST_F(UtilityProcessMojoClientBrowserTest, ConnectionError)
{
    base::RunLoop run_loop;
    done_closure_ = run_loop.QuitClosure();

    StartMojoService(false);

    mojo_client_->service()->DoTerminateProcess(
        base::Bind(&UtilityProcessMojoClientBrowserTest::OnResponseReceived,
            base::Unretained(this)));

    run_loop.Run();
    EXPECT_FALSE(response_received_);
    EXPECT_TRUE(error_happened_);
}

// Android doesn't support non-sandboxed utility processes.
#if !defined(OS_ANDROID)
// Call a function that fails because the sandbox is still enabled.
IN_PROC_BROWSER_TEST_F(UtilityProcessMojoClientBrowserTest, SandboxFailure)
{
    base::RunLoop run_loop;
    done_closure_ = run_loop.QuitClosure();

    StartMojoService(false);

    mojo_client_->service()->CreateFolder(
        base::Bind(&UtilityProcessMojoClientBrowserTest::OnCreateFolderFinished,
            base::Unretained(this)));

    run_loop.Run();
    EXPECT_TRUE(response_received_);
    // If the sandbox is disabled by the command line, this will succeed.
    if (!base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kNoSandbox))
        EXPECT_FALSE(sandbox_succeeded_);
    EXPECT_FALSE(error_happened_);
}

// Call a function that succeeds only when the sandbox is disabled.
IN_PROC_BROWSER_TEST_F(UtilityProcessMojoClientBrowserTest, SandboxSuccess)
{
    base::RunLoop run_loop;
    done_closure_ = run_loop.QuitClosure();

    StartMojoService(true);

    mojo_client_->service()->CreateFolder(
        base::Bind(&UtilityProcessMojoClientBrowserTest::OnCreateFolderFinished,
            base::Unretained(this)));

    run_loop.Run();
    EXPECT_TRUE(response_received_);
    EXPECT_TRUE(sandbox_succeeded_);
    EXPECT_FALSE(error_happened_);
}
#endif // !defined(OS_ANDROID)

#if defined(OS_WIN)
// Call a function that succeeds with process elevation. Elevation is only
// available on WIN, and is used to permit UAC prompts for operations that
// need user approval before proceeding.
IN_PROC_BROWSER_TEST_F(UtilityProcessMojoClientBrowserTest, ElevatedSuccess)
{
    base::RunLoop run_loop;
    done_closure_ = run_loop.QuitClosure();

    bool elevated_utility_process = true;
    StartMojoService(true, elevated_utility_process);

    mojo_client_->service()->CreateFolder(
        base::Bind(&UtilityProcessMojoClientBrowserTest::OnCreateFolderFinished,
            base::Unretained(this)));

    run_loop.Run();
    EXPECT_TRUE(response_received_);
    EXPECT_TRUE(sandbox_succeeded_);
    EXPECT_FALSE(error_happened_);
}
#endif // defined(OS_WIN)

} // namespace content
